/**
 * 
 */
package com.mrroman.linksender.sender;

import com.mrroman.linksender.Configuration;
import com.mrroman.linksender.MessageParser;
import com.mrroman.linksender.ioc.In;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.ResourceBundle;
import java.util.logging.Logger;

import com.mrroman.linksender.ioc.Locales;
import com.mrroman.linksender.ioc.Log;
import com.mrroman.linksender.ioc.Name;

/**
 * @author konrad
 *
 */
@Name("sender.MessagePoint")
public class MessagePoint implements Runnable {

    public static final int BUFFER_SIZE = 4096 * 1;
    @Log
    private Logger logger;
    @In
    private Configuration config;
    @In
    private MessageParser messageParser;
    @Locales
    private ResourceBundle messages;
    private MulticastSocket socket;
    private InetAddress group;
    private int port;
    private boolean loopbackMode = true;
    private boolean started;
    private List<MessageListener> listeners;

    public void setServer(String address, int port) {
        try {
            this.group = InetAddress.getByName(address);
        } catch (UnknownHostException e) {
            throw new IllegalArgumentException(e);
        }
        this.port = port;
        this.listeners = new ArrayList<MessageListener>();
    }

    public void setLoopbackMode(boolean mode) {
        this.loopbackMode = !mode;
    }

    public void addMessageListener(MessageListener listener) {
        synchronized (listeners) {
            listeners.add(listener);
        }
    }

    public void removeMessageListener(MessageListener listener) {
        synchronized (listeners) {
            listeners.remove(listener);
        }
    }

    public void run() {
        try {

            socket = new MulticastSocket(port);
            socket.joinGroup(group);
            socket.setLoopbackMode(loopbackMode);

            logger.info(messages.getString("server_started"));

            started = true;

            byte[] buf = new byte[BUFFER_SIZE];
            DatagramPacket recv = new DatagramPacket(buf, buf.length);

            while (started) {
                try {
                    socket.receive(recv);
                    sendEvent(new MessageEvent(getMessage(recv)));
                } catch (SocketException ex) {
                    // if we close socket, exception is thrown
                }
            }

        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    public boolean isStarted() {
        return started;
    }

    public void stopServer() {
        try {
            socket.leaveGroup(group);
            socket.close();
            logger.info(messages.getString("server_stopped"));
        } catch (IOException e) {
            e.printStackTrace();
        }
        started = false;
    }

    private void sendEvent(MessageEvent event) {
        for (MessageListener listener : listeners) {
            listener.messageSend(event);
        }
    }

    private void messageHasBeenSent(Message msg) {
        if (config.isLoopbackMode()) {
            return;
        }
        Message copyOfMsg = new Message(messages.getString("send_message_dialog_all"), msg.getDate(), msg.getMessage(), msg.getFlags() | Message.MESSAGE_FLAG_SELF);
        for (MessageListener listener : listeners) {
            MessageEvent event = new MessageEvent(MessageEvent.EventType.MESSAGE, copyOfMsg);
            listener.messageSend(event);
        }
    }

    public synchronized void sendMessage(Message message) {
        try {
            ByteArrayOutputStream bo = new ByteArrayOutputStream(BUFFER_SIZE);
            ObjectOutputStream oo = new ObjectOutputStream(bo);

            oo.writeObject(message);
            oo.flush();
            oo.close();

            if (bo.size() > BUFFER_SIZE) {
                sendEvent(new MessageEvent(MessageEvent.EventType.ERROR, "system", messages.getString("message_too_long") + "\n\"" + message.getMessage().replace("\n", " ").trim().substring(0, 40) + "...\""));
                return;
            }

            DatagramPacket packet = new DatagramPacket(bo.toByteArray(), bo.size(), group, port);
            socket.send(packet);

            bo.close();

            if ((message.getFlags() & Message.MESSAGE_FLAG_HIDDEN) > 0) {
                logger.finest(messages.getString("message_sent") + " " + message);
            } else {
                logger.info(messages.getString("message_sent") + " " + message);
            }
            messageHasBeenSent(message);
        } catch (IOException ex) {
            sendEvent(new MessageEvent(MessageEvent.EventType.INFO, "system", ex.getMessage()));
        }
    }

    private Message getMessage(DatagramPacket packet) {
        try {
            ObjectInputStream oi = new ObjectInputStream(
                    new ByteArrayInputStream(packet.getData(), packet.getOffset(), packet.getLength()));
            Object o = oi.readObject();

            if (!(o instanceof Message)) {
                logger.info(messages.getString("message_recieved") + " " + o);
                logger.severe(messages.getString("wrong_packet"));
                oi.close();
                return null;
            } else {
                oi.close();
                Message message = (Message) o;
                message.setAddressOfSender(packet.getAddress());
                if ((message.getFlags() & Message.MESSAGE_FLAG_HIDDEN) > 0) {
                    logger.finest(messages.getString("message_recieved") + " " + message);
                } else {
                    logger.info(messages.getString("message_recieved") + " " + message);
                }
                message.setMessage(messageParser.replaceSenderInMessage(message));
                //System.out.println(message.toString());
                return message;
            }
        } catch (IOException ex) {
            logger.severe(ex.getMessage());
            sendEvent(new MessageEvent(MessageEvent.EventType.INFO, "system", ex.getMessage()));
            return null;
        } catch (ClassNotFoundException ex) {
            logger.severe(messages.getString("wrong_packet") + ": " + ex.getMessage());
            sendEvent(new MessageEvent(MessageEvent.EventType.ERROR, "system", messages.getString("wrong_packet") + ": " + ex.getMessage()));
            return null;
        }
    }
}
